package ftracer

import (
	"context"
	"fmt"
	"net/http"
	"sync"

	"gitee.com/wu-jin-feng/fgin/fgin_plugs/fotel"
	"github.com/gin-gonic/gin"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/trace"
	"google.golang.org/grpc/metadata"
)

// tracer的配置
type options struct {
	tracerName     string
	tracerProvider trace.TracerProvider
}

// Tracer 对象
type Tracer struct {
	tracer trace.Tracer
	kind   trace.SpanKind
	opt    *options
}

// 新建一个自定义tracer对象
func NewTracer(kind trace.SpanKind, tracerName string) *Tracer {
	if !fotel.GetTracerStatus() {
		panic("fotel尚未初始化tracer")
	}
	op := options{
		tracerName: tracerName,
	}
	if op.tracerProvider == nil {
		op.tracerProvider = otel.GetTracerProvider()
	}

	switch kind {
	case trace.SpanKindClient:
		return &Tracer{tracer: op.tracerProvider.Tracer(op.tracerName), kind: kind, opt: &op}
	case trace.SpanKindServer:
		return &Tracer{tracer: op.tracerProvider.Tracer(op.tracerName), kind: kind, opt: &op}
	default:
		panic(fmt.Sprintf("没有当前span类型: %v", kind))
	}
}

// 新建一个span
func (t *Tracer) Start(ctx context.Context, operation string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
	// 读取跨端端链路追踪
	ctx = extractFromCtx(ctx)
	// 追加其他配置
	opts = append(opts, trace.WithSpanKind(t.kind))
	ctx, span := t.tracer.Start(ctx,
		operation,
		opts...,
	)
	return ctx, span
}

// span 结束
func (t *Tracer) End(span trace.Span, err error, opts ...trace.SpanEndOption) {
	if err != nil {
		span.RecordError(err)
		span.SetStatus(codes.Error, err.Error())
	} else {
		span.SetStatus(codes.Ok, "OK")
	}
	span.End(opts...)
}

// 读取traceId
func extractFromCtx(ctx context.Context) context.Context {
	propagator := otel.GetTextMapPropagator()
	// http部分的
	if v, ok := ctx.(*gin.Context); ok {
		// gin 类型从header读取traceId
		return propagator.Extract(v.Request.Context(), propagation.HeaderCarrier(v.Request.Header))
	}
	// grpc部分
	md, _ := metadata.FromIncomingContext(ctx)
	md = alertMd(md, "traceparent", "Traceparent")
	return propagator.Extract(ctx, propagation.HeaderCarrier(md))
}

// tracer注入到http中传递
func HttpInject(spanCtx context.Context, header http.Header) http.Header {
	propagator := otel.GetTextMapPropagator()
	propagator.Inject(spanCtx, propagation.HeaderCarrier(header))
	return header
}

// tracer 注入到grpc中传递
func GrpcInject(spanCtx, ctx context.Context) context.Context {
	// md := metadata.MD{}
	md, ok := metadata.FromOutgoingContext(ctx)
	if !ok {
		md = metadata.MD{}
	}
	propagator := otel.GetTextMapPropagator()
	propagator.Inject(spanCtx, propagation.HeaderCarrier(md))
	md = alertMd(md, "Traceparent", "traceparent")
	return metadata.NewOutgoingContext(ctx, md)
}

// 从context获取traceId
func GetTraceIdFromContext(ctx context.Context) string {
	if v, ok := ctx.(*gin.Context); ok {
		// gin 类型
		ctx = v.Request.Context()
	}
	traceId := ""
	if trace.SpanContextFromContext(ctx).HasTraceID() {
		traceId = trace.SpanContextFromContext(ctx).TraceID().String()
	}

	return traceId
}

// 将Traceparent 转为小写
func alertMd(md metadata.MD, flag1, flag2 string) metadata.MD {
	if value, ok := md[flag1]; ok {
		var mu sync.Mutex
		mu.Lock()
		delete(md, flag1)
		md[flag2] = value
		mu.Unlock()
	}
	return md
}
